# fastmcp_server.py  ——  人类可读输出版
from __future__ import annotations
import logging
import os
import re
from datetime import datetime, timedelta
from typing import Dict, List, Any
from functools import wraps

from fastmcp import FastMCP
import mcp.types as types
from zoneinfo import ZoneInfo
from tzlocal import get_localzone_name
from logging.handlers import TimedRotatingFileHandler

# ---------------- 1. 日志配置 -----------------
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
LOG_FMT = "%(asctime)s | %(levelname)8s | %(name)s | %(funcName)s:%(lineno)d | %(message)s"

os.makedirs("logs", exist_ok=True)
file_handler = TimedRotatingFileHandler(
    filename="logs/mcp-time-server.log",
    when="midnight",
    interval=1,
    backupCount=14,
    encoding="utf-8"
)
file_handler.setFormatter(logging.Formatter(LOG_FMT))
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter(LOG_FMT))

logging.basicConfig(level=LOG_LEVEL, handlers=[console_handler, file_handler], force=True)
logger = logging.getLogger("mcp-time-server")

# ---------------- 工具异常回传装饰器 -----------------
def return_error(func: Any) -> Any:
    @wraps(func)
    async def wrapper(*args: Any, **kwargs: Any) -> List[types.ContentBlock]:
        try:
            return await func(*args, **kwargs)
        except Exception as exc:
            logger.exception("Tool %s failed", func.__name__)
            return [types.TextContent(type="text", text=f"[ERROR] {exc}")]
    return wrapper

# ---------------- 参数验证辅助函数 -----------------
def _validate_timezone(timezone: str) -> None:
    """验证时区格式"""
    if not timezone or not isinstance(timezone, str):
        raise ValueError("[ERROR-TZ-001] 时区名称不能为空")
    # 基础格式验证：包含斜杠，且两部分都为非空字符串
    parts = timezone.split('/')
    if len(parts) < 2 or not all(part.strip() for part in parts):
        raise ValueError("[ERROR-TZ-002] 时区格式错误，应为 Area/Location 格式，如 'Asia/Shanghai'")

def _validate_time_format(time_str: str) -> None:
    """验证时间格式 HH:MM"""
    if not re.fullmatch(r"\d{2}:\d{2}", time_str):
        raise ValueError("[ERROR-TIME-001] 时间格式错误，应为 HH:MM，如 09:30")
    try:
        datetime.strptime(time_str, "%H:%M")
    except ValueError:
        raise ValueError("[ERROR-TIME-002] 无效时间，小时应在 00-23，分钟应在 00-59")

# ---------------- 2. FastMCP 实例 -----------------
mcp = FastMCP("mcp-time-server")

# ---------------- 3. 工具函数 -----------------
def get_local_tz(local_tz_override: str | None = None) -> ZoneInfo:
    """获取本地时区"""
    if local_tz_override:
        logger.debug("使用覆盖时区: %s", local_tz_override)
        return ZoneInfo(local_tz_override)
    local_tzname = get_localzone_name()
    if local_tzname is None:
        logger.error("无法确定本地时区")
        raise ValueError("无法确定本地时区")
    logger.debug("本地时区: %s", local_tzname)
    return ZoneInfo(local_tzname)


def get_zoneinfo(timezone_name: str) -> ZoneInfo:
    """获取 ZoneInfo 对象"""
    try:
        tz = ZoneInfo(timezone_name)
        logger.debug("时区解析成功: %s", timezone_name)
        return tz
    except Exception as e:
        logger.warning("无效时区: %s , error: %s", timezone_name, e)
        raise ValueError(f"无效时区: {timezone_name}")


# ---------------- 4. 工具注册 -----------------
@mcp.tool()
@return_error
async def get_current_time(timezone: str) -> List[types.ContentBlock]:
    """
    获取指定时区的当前时间。

    参数
    ----
    timezone : str
        必填。IANA 时区名称，例如 'Asia/Shanghai'。

    返回
    ----
    List[types.TextContent]
        纯文本，示例如下：
        北京时间（Asia/Shanghai）现在：
        2025-10-13 14:30:45 (夏令时: 否)
    """
    _validate_timezone(timezone)
    
    logger.info("[get_current_time] receive timezone=%s", timezone)
    tz = get_zoneinfo(timezone)
    now = datetime.now(tz)
    tz_friendly = timezone.replace("_", " ")
    dst = "是" if now.dst() else "否"
    
    result = (
        f"{tz_friendly} 现在：\n"
        f"{now.strftime('%Y-%m-%d %H:%M:%S')} \n"
        f"夏令时: {dst}"
    )
    return [types.TextContent(type="text", text=result)]


@mcp.tool()
@return_error
async def convert_time(source_timezone: str, time: str, target_timezone: str) -> List[types.ContentBlock]:
    """
    在不同时区之间转换时间。

    参数
    ----
    source_timezone : str
        必填。源时区 IANA 名称。
    time : str
        必填。24 小时制时间，格式 HH:MM，例如 "09:30"。
    target_timezone : str
        必填。目标时区 IANA 名称。

    返回
    ----
    List[types.TextContent]
        纯文本，示例如下：
        当纽约（America/New_York）09:30 时：
        上海（Asia/Shanghai）为 21:30（+12h）
    """
    _validate_timezone(source_timezone)
    _validate_timezone(target_timezone)
    _validate_time_format(time)
    
    logger.info("[convert_time] receive src=%s time=%s tgt=%s", source_timezone, time, target_timezone)
    src_tz = get_zoneinfo(source_timezone)
    tgt_tz = get_zoneinfo(target_timezone)

    parsed_time = datetime.strptime(time, "%H:%M").time()
    now = datetime.now(src_tz)
    src_dt = datetime(
        now.year, now.month, now.day,
        parsed_time.hour, parsed_time.minute,
        tzinfo=src_tz
    )
    tgt_dt = src_dt.astimezone(tgt_tz)

    diff = (tgt_dt.utcoffset() - src_dt.utcoffset()).total_seconds() / 3600
    diff_str = f"{diff:+.0f}h"

    src_name = source_timezone.replace("_", " ")
    tgt_name = target_timezone.replace("_", " ")

    result = (
        f"当 {src_name} {time} 时：\n"
        f"{tgt_name} 为 {tgt_dt.strftime('%H:%M')}（{diff_str}）"
    )
    return [types.TextContent(type="text", text=result)]


# ---------------- 5. 启动入口 -----------------
if __name__ == "__main__":
    logger.info("Starting mcp-time-server on 0.0.0.0:7872")
    try:
        mcp.run(transport="streamable-http", host="0.0.0.0", port=7872)
    except TypeError:  # 旧版 fastmcp 回退
        import uvicorn
        uvicorn.run("fastmcp_server:mcp", host="0.0.0.0", port=7872)